﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GraphAlgorithms;

namespace MachineLearning {
    /// <summary>
    /// each perceptron has a one output and a list of inputs
    /// </summary>
    class NeuralUnit {
        public Output Oput = null;
        double _oputPrime = 0.0;
        public double OputPrime {
            get { return _oputPrime; }
        }

        Input[] _inputs = null;
        public Input[] Inputs {
            get { return _inputs; }
            set { _inputs = value; }
        }

        //all neurons have an x0 input
        Input _xZero = null;
        public Input XZero {
            get { return _xZero; }
        }
        double _error = 0.0;
        public double Error {
            get { return _error; }
            set { _error = value; }
        }

        //the name of the unit
        string _name = null;
        public string Name {
            get { return _name; }
            set { _name = value; }
        }

        //the name of the unit
        string[] _connectedUnits = null;
        public string[] ConnectedUnits {
            get { return _connectedUnits; }
            set { _connectedUnits = value; }
        }

        //squashing function used, sigmoid, tanh, etc
        SquashingFunction _squash = null;

        //default constructor
        public NeuralUnit() {
            initialize();
            _squash = new Sigmoid();
        }

        public NeuralUnit(SquashingFunction sq) {
            initialize();
            _squash = sq;
        }

        public NeuralUnit(double weight, SquashingFunction sq) {
            initialize();
            _xZero.Weight = weight;
            _squash = sq;
        }

        /// <summary>
        /// efficient computation of the unit's output
        /// </summary>
        double _temp = 0.0;
        public void computeOutput() {
            _temp = 0.0;
            foreach (Input i in Inputs)
                _temp += i.Weight * i.Oput.Value;
            Oput.setValue(_squash.squash(_temp));
            _oputPrime = _squash.squashPrime(Oput.Value);
        }

        //compute output, given input, no squashing function applied
        public void computeOutput(double input) {
            Oput.setValue(input);
        }

        //called by constructor
        void initialize() {
            Oput = new Output();
            _xZero = new Input();
            _xZero.Oput = new Output();
            _xZero.Oput.setValue(1.0);
        }
    }

    class Output {
        double _vlaue = 0.0;
        public double Value {
            get { return _vlaue; }
        }

        //the name of the output
        string _name = null;
        public string Name {
            get { return _name; }
            set { _name = value; }
        }

        public void setValue(double x) {
            _vlaue = x;
        }

    }

    /// <summary>
    /// each input has a weight and an input
    /// </summary>
    class Input {
        double _weight = 0.0;
        public double Weight {
            get { return _weight; }
            set { _weight = value; }
        }

        double _momentum = 0.0;
        public double Momentum {
            get { return _momentum; }
            set { _momentum = value; }
        }

        //the output that is plugged into the input
        Output _outputInstance;
        public Output Oput {
            get { return _outputInstance; }
            set { _outputInstance = value; }
        }

        //the name of the input
        string _name = null;
        public string Name {
            get { return _name; }
            set { _name = value; }
        }

        public Input() {}

        //just initialize the weight
        public Input(double weight) {
            _weight = weight;
        }

        //plug the output into the input
        public Input(ref Output outInst) {
            _outputInstance = outInst;
        }

        //plug the output into the input
        public Input(ref Output outInst, double weight) {
            _outputInstance = outInst;
            _weight = weight;
        }
    }
}
